Docker 基础操作
Table of Contents
1. 安装
docker 按照维护者不同,常用的有两个版本: Debian 打包的 docker.io 以及 Docker 官方维护的 docker-ce 。
虽然 docker-ce 版本较高,但它把所有的依赖都糊在一个二进制文件里,要打补丁只能升级 docker 版本,所以还是推荐安装 Debian 打包 ( 外部依赖分别安装 ) 的 docker.io。
1.1. docker.io
docker.io 可以直接用命令 sudo apt install docker.io
安装。
为了让 lsz 用户在使用 docker 时不用在命令前加 sudo,执行 sudo usermod -aG docker lsz
把 lsz 用户加入 docker 用户组。
ssh 重连后生效。
1.2. docker-ce
安装 docker-ce 可以使用官方的一键安装脚本:
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
也可以手动安装:
如果过去安装过 docker,先删掉 sudo apt-get remove docker docker-engine docker.io
。
接着安装依赖 sudo apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common
。
添加 Docker 的 GPG 公钥:
- Debian 系统:
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
- Ubuntu 系统:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
对于 amd64 架构的机器,添加软件仓库:
- Debian 系统:
sudo add-apt-repository \ "deb [arch=amd64] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/debian \ $(lsb_release -cs) \ stable"
- Ubuntu 系统:
sudo add-apt-repository \ "deb [arch=amd64] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu \ $(lsb_release -cs) \ stable"
如果用树莓派或其它 ARM 架构机器,运行:
echo "deb [arch=armhf] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/debian \ $(lsb_release -cs) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list
最后更新源 sudo apt-get update
安装 sudo apt-get install docker-ce
。
2. 配置
基本的 /etc/docker/daemon.json 配置为:
{
"registry-mirrors": [
"http://registry.docker-cn.com",
"http://docker.mirrors.ustc.edu.cn",
"http://hub-mirror.c.163.com"
],
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "5"
}
}
2.1. 增加拉取 docker 镜像的加速
2.1.1. 使用国内镜像源
在 /etc/docker/daemon.json 文件里加入以下内容:
{
//...
"registry-mirrors": [
"http://registry.docker-cn.com",
"http://docker.mirrors.ustc.edu.cn",
"http://hub-mirror.c.163.com"
]
//...
}
sudo systemctl daemon-reload
重新加载配置文件, sudo systemctl restart docker
重启 docker。
2.1.2. 给 dockerd 进程添加代理
经测试,在 x86 设备可行。
docker pull
由守护进程 dockerd 执行,所以代理要配给这个进程。而这个进程受 systemd 管控,所以要写一个 systemd 配置。
sudo mkdir -p /etc/systemd/system/docker.service.d sudo vim /etc/systemd/system/docker.service.d/proxy.conf
在这个文件中添加以下内容 (代理服务器配置自行替代):
[Service] Environment="HTTP_PROXY=http://192.168.1.11:10809/" Environment="HTTPS_PROXY=http://192.168.1.11:10809/" Environment="NO_PROXY=localhost,127.0.0.1"
最后执行:
sudo systemctl daemon-reload sudo systemctl restart docker
让配置生效。
2.2. 容器日志设置
在 /etc/docker/daemon.json 文件里加入以下内容:
{
//...
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "5"
}
//...
}
这表示容器日志的格式为 json 文件,每个容器会产生最多 5 个日志文件,每个文件最大 10m,即最多 50M 的日志。
sudo systemctl daemon-reload
重新加载配置文件, sudo systemctl restart docker
重启 docker 使配置生效。
2.3. docker 程序环境
环境配置文件:
- /etc/sysconfig/docker-network
- /etc/sysconfig/docker-storage
- /etc/sysconfig/docker
- Unit File:
- /usr/lib/systemd/system/docker.service
- Docker Registry 配置文件:
- /etc/containers/registries.conf
- docker daemon 配置文件: /etc/docker/daemon.json
2.4. x86 设备报错
2.4.1. WARNING: No swap limit support
docker run ... -m 64M --memory-swap=128M ...
可以用来限制容器的最大内存使用情况:
-m 64M
限制了容器运行可以使用的最大物理内存为 64M;--memory-swap=128M
限制容器可以使用的最 swap 空间为 128M;
在出现 WARNING: No swap limit support
警告时这两个参数是无效的。
原因是内核的 swap 功能没开,开了就行了。
编辑配置文件 /etc/default/grub,文件内部大概长这样:
# If you change this file, run 'update-grub' afterwards to update # /boot/grub/grub.cfg. # For full documentation of the options in this file, see: # info -f grub -n 'Simple configuration' GRUB_DEFAULT=0 GRUB_TIMEOUT=5 GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian` GRUB_CMDLINE_LINUX_DEFAULT="quiet" GRUB_CMDLINE_LINUX="" ......
在 GRUB_CMDLINE_LINUX=""
的双引号中添加内容成 GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"
。注意,如果双引号内有内容则原内容不能改变,新内容和原内容也要用空格隔开。
然后 sudo update-grub
更新配置, sudo reboot
重启机器。
2.5. arm ( 树莓派 ) 设备报错
2.5.1. 容易解决的报错
树莓派第一次安装完 docker 之后, docker info
报错基本都是这样的:
WARNING: No memory limit support WARNING: No swap limit support WARNING: No kernel memory limit support WARNING: No kernel memory TCP limit support WARNING: No oom kill disable support
编辑 /boot/cmdline.txt 文件,在其后加入 cgroup_enable=memory swapaccount=1
两个参数,重启系统就解决了。
2.5.2. 不支持 cfs 特性
如果报错出现:
WARNING: No cpu cfs quota support WARNING: No cpu cfs period support
一般树莓派系统才会报这个错。
原因是早期的 Raspbian 系统在编译时就没支持 cfs。
早期没办法处理。
但是 2020 年末支持了这几个特性。
2.5.3. 不支持 blkio 特性
如果报错出现:
WARNING: No blkio weight support WARNING: No blkio weight_device support
使用 SATA3 硬盘时候没有这个问题,在换成 SATA2 的硬盘以后问题出现。解决方案未知。
3. docker 命令
3.1. 基础操作
docker <command>
这种格式的命令与 docker image <command>
或者 docker container <command>
的功能有重叠的部分。 docker <command>
格式出现比较早,没有指明操作对象,所以推荐用指明操作对象的后一种格式的命令。
-d
参数表示在后台运行容器。
-e
选项用于设置容器内的环境变量。格式为 -e var=value
。
查看针对 image 或 container 的命令 docker ( image | container ) help
。
容器运行时指定容器内主机的主机名 docker run --hostname <name> <other_command>
。
3.1.1. 打包镜像
将镜像文件导出为 tar 文件: docker save -o <file-name>.tar.gz repo/<image-name>:<tag> [repo/<image-name-2>:<tag>]
(可以把多个镜像打包在一个 tar 文件中)
从 tar 文件导入镜像 docker load -i <image_name>.tar.gz
。
3.1.2. 容器自启动
docker run --restart=always --name <container_name>
容器总是自启动。
docker run --restart=unless-stopped
除非手动停止容器,否则容器总是自启动。
docker update --restart=no <container_name>
容器不会自启动。
3.1.3. 与容器交互 ( -it )
docker exec -it <container_name> /bin/sh
可以以新建一个交互式 shell 终端的方式连接到容器。
docker attach <container_name>
可以直接连接到容器。
3.1.3.1. -i 选项
-i
的含义是为容器开启一个 stdin 以读取输入。
所以可以有这样的操作:
echo some input | docker run -i debian cat
这个例子中,echo 命令的输出将通过管道传输到容器的 stdin 中 (由 -i
选项开启),容器将从 stdin 读到的数据传输给容器内的,需要从 stdin 读取输入的程序 (cat 程序),最终得到执行结果。即,输出 some input
。
3.1.3.2. -t 选项
-t
的含义是为容器分配一个虚拟的 tty 终端。
tty 是一个软件或者硬件,它连接到 linux 系统用于读取用户输入,并把输入处理成标准输入流发送到 stdin,然后程序从 stdin 中读取输入的数据并做出响应。
如果不使用 -i
给容器开启一个 stdin 的话,用户的输入不被任何程序处理就会出现用户无法退出这个 tty 的情况。
3.1.3.3. 和容器交互完怎么退出
用户连接到容器的情景一般有两个:
- 容器启动时在 docker run 命令中指定
-i
但不指定-d
,容器启动后当前终端直接连接到容器的 stdin - 使用 docker attach 命令连接到已经启动的容器的 stdin
对于后一种连接到容器的情景来说,断开与容器的连接的最简单的方案是找到 docker attach 进程的 pid 再用 kill 杀掉这个进程。
而对于前一种情景来说,则需要分别讨论容器的启动参数 (见后文的例子)。
在指定 -i 不指定 -t 的情况下,默认有一个 --sig-proxy=true
的选项生效,这个选项的作用是把从 stdin 读到的除 SIGCHLD, SIGKILL 和 SIGSTOP 之外的信号转发到容器内 pid 为 1 的进程。
注: --sig-proxy
参数只在 non-tty (即,不使用 -t
选项) 的模式下起作用
所以用户输入 Ctrl-c 发出的信号会被 docker 转发到容器内 pid 为 1 的进程,使得进程退出,进而导致容器退出。
注: 在 foreground mode (即不使用 -d
选项) 时,当前终端会被连接到容器的 stdin 上
第一个例子: docker run -i --rm nginx
起一个 nginx 容器,然后使用 Ctrl-c 发送退出信号之后,容器里的 nginx 退出了,接着容器也退出了。在这个例子中,无法在不结束容器的情况下退出与容器的交互模式。
第二个例子: docker run -i --sig-proxy=false --rm nginx
启动容器再用 Ctrl-c 发送退出信号,那么这个退出信号会由 docker attach 处理,不会被转发到容器内,所以可以使用 Ctrl-c 退出交互模式而不影响容器内进程。(这个例子中 Ctrl-c 的效果是取消与 stdin 的连接)
第三个例子: docker run -i -d --rm --name nginx_tt nginx
起一个 nginx 容器。再用 docker attach nginx_tt
连接到容器。需要注意,用户此时通过 docker attach 这个进程连接到容器,要退出与容器的交互模式,需要另起一个终端连接到宿主机,用 kill 杀掉 docker attach 进程才能保证容器不退出。(当前终端通过 docker attach 这个进程连接到容器的 stdin, docker attach 进程退出后与 stdin 的连接也就中断了)
第四个例子: docker run -i -d --rm --name nginx_tt nginx
起一个 nginx 容器。再用 docker attach --sig-proxy=false nginx_tt
连接到容器。此时用户输入的 Ctrl-c 会被 docker attach 进程截获并处理,效果就是用户只退出了 docker attach 进程,而容器不受影响。(这个例子中的 Ctrl-c 使 docker attach 进程退出,从而断开与容器的 stdin 的连接)
在单指定 -t
的情况下,想要退出交互只能另起终端再用 kill 命令,因为容器启动后,用户终端连接到分配给容器的虚拟 tty上,但容器没有 stdin,用户发出的信号没有程序能读到,也就退不出这个 tty,只能从外部杀掉才能退出。
比如 docker run -t -d --name nginx_t nginx && docker attach nginx_t
(可以尝试) 执行后,用户根本退不出去,只能通过结束当前终端重新连接,或者起一个新终端再用 kill 杀掉 docker attach 进程才能使连接到容器的终端退出。
3.2. 容器网络
运行无网络的容器 docker run --network none <other-command>
。
docker run --network=host
使用物理机的网络,不做映射。
容器端口映射:
docker run ... --network=bridge -p <container_port>
容器端口映射至主机地址的一个随机端口。docker run ... --network=bridge -p <host_port>:<container_port>
容器端口映射到指定的主机端口。docker run ... --network=bridge -p <ip>::<container_port>
容器端口映射到 ip 指定的主机的随机端口。docker run ... --network=bridge -p <ip>:<host_port>:<container_port>
容器端口映射到 ip 指定的主机的端口。
-p
( 小写 ) 参数可以使用多次来暴露多个端口。
-P
( 大写 ) 或者 --publish-all
参数可以映射 Dockerfile 中 EXPOSE 命令指定的每个端口到主机的随机端口上,写法是 docker run -P ...
。也可以在后面加 --expose
参数来追加需要映射的端口,写法是 docker run ... -P --expose 2222 --expose 3333
。
主机上随即被映射的端口可以在防火墙规则中查询,也可以使用 docker port <container_name>
查询。
3.3. 容器存储卷
在容器中挂载目录,如果要挂在的目录不存在则自动创建:
docker run --name <container_name> -v /data <image_name> <other_command>
把容器的 /data 目录交给 docker 管理并映射到本机的 /data 目录。docker run --name <container_name> -v <hostDir>:<volumeDir>
把宿主机上的 hostDir 映射到容器内的 volumeDir。
执行 docker inspect -f {{.Mounts}} <container_name>
查看挂载关系。
多个容器可共享一个存储卷。
新启动的容器可以复制已启动容器的存储卷的映射关系: docker --volume-from <container_name>
。
列出所有不被挂载的容器卷 docker volume ls -qf dangling=true
。
删除所有不被挂载的容器卷 docker volume rm $(docker volume ls -qf dangling=true)
。
4. Dockerfile
每一条 Dockerfile 指令都会生成一个单独的层,所以,尽量合并指令。
Dockerfile
是 dockerfile 的默认文件名,首字母必须大写,而且要有一个专用的工作目录。
docker build -f <docker-file>
可以指定 dockerfile。
.dockerignore 类似 .gitignore ,该文本文件中指明的文件不会被打包进镜像。
- FROM 命令
指定基础镜像。
FROM <repository>[:<tag>]
:<repository>
用于指定基础镜像的名称,<tag>
缺省时默认为latest
。FROM <repository>@<digest>
:<digest>
用于指定镜像的哈希码。
- COPY 命令
从宿主机复制文件到镜像中。
COPY <file> <dest-path>
: 把宿主机上的<file>
文件复制到镜像的<dest-path>
目录。COPY ["file-1", "file-2"..."file-n", "dest"]
: 一次性复制多个文件到镜像中。文件名支持通配符。- 如果
<file>
是目录,其内部子文件会被递归复制,但目录本身不会被复制。 - 如果指定了多个
<file>
或者使用了通配符,则<dest>
必须是一个目录且必须以/
结尾。 - 如果
<dest>
事先不存在则会被自动创建。
- ADD 指令
类似于 COPY 命令。
- ADD 格式与 COPY 相同。
- ADD 支持 tar 文件与 url 路径。
- 如果 ADD 了一个本地压缩文件,这个文件进入镜像之后会被自动解压缩到目标目录下。
- 如果从 url 下载了一个压缩文件,则不会被自动解压。
- WORKDIR 指令
用于为 dockerfile 中所有的 RUN, CMD, ENTERYPOINT, COPY 和 ADD 指定工作目录。
WORKDIR <path>
指令可出现多次,<path>
可以是相对路径,但是,相对路径是相对于前一个<path>
的相对路径。<path>
也可以是环境变量中指定的路径,类似$STATEPATH
。
- VOLUME 指令
用于在镜像随创建一个挂载点目录。
VOLUME <mountPoint>
或者VOLUME ["<mountPoint>", "<mountPoint>"..]
可挂载多个卷。- 不可指定宿主机的映射点,docker 会在 /var/lib/docker 文件夹下创建映射目录。
- EXPOSE 指令
指定容器中需要暴露的端口。
EXPOSE <port>[/<protocol>] [<port>[/<protocol>]]
:<port>
指定端口,<protocol>
指定协议,tcp, udb 二选一,默认 tcp。- 随机绑定到宿主机上的端口,可能出现端口冲突。
- 运行镜像时需要使用
docker rum -P
手动操作一下才会暴露命令中指定的端口。
- ENV 指令
为镜像定义所需的环境变量,可以被 Dockerfile 文件中位于其后的其他指令调用。
- 定义的环境变量最终也将作用于容器中。
- 命令格式:
ENV <key> <value>
或者ENV <key>=<value> [<key>=<value> ...]
。- 第二种格式的
<value>
如果包含空格,可以使用\
进行转义或者使用"value"
添加引号进行标识。 - 反斜线
\
也可用于续行。
- 调用格式
$variable_name
或者$<variable_name>
。
- RUN 指令
定义
docker build
过程中要用的 shell 命令。FROM
指定的基础镜像中应该包含被执行的命令。RUN <command>
或RUN ["<executable>", "<param1>", "<param2>"...]
:<command>
被/bin/sh -c
运行,意味着此进程在容器中的 PID 不为 1。- 当使用
docker stop <containerName>
停止容器时,被/bin/sh -c
产生的进程接收不到 SIGTERM 信号。 - 如果要执行的命令依赖 shell 特性 ( 比如要引用环境变量 ),可以指定运行
RUN ["/bin/bash", "-c", "<executable>", "<param1>", "<param2>"...]
。
- 多个命令用
&&
进行并列,可用\
续行。
- CMD 指令
定义 docker run 时要用的 shell 命令。
- CMD 指定的命令执行完毕后,容器终止。
- CMD 命令可以被 docker run 的命令行选项覆盖。
- Dockerfile 中可以有多个 CMD 命令,但只有最后一个会生效。
CMD <COMMAND>
与CMD ["<executable>", "<param1>", "<param2>"...]
意义同 RUN 命令。
- 如果要执行的命令依赖 shell 特性 ( 比如要引用环境变量 ),可以指定运行
CMD ["/bin/bash", "-c", "<executable>", "<param1>", "<param2>"...]
。 CMD ["<param1>", "<param2>"...]
用于为 ENTRYPOINT 指令提供默认参数。
- ENTRYPOINT 指令
类似 CMD,为容器指定默认运行的程序。
- Dockerfile 中可以有多个 ENTRYPOINT 命令,但只有最后一个会生效。
- 如果既有 CMD 又有 ENTRYPOINT,CMD 内容会被作为参数传给 ENTRYPOINT。
- 格式为
ENTRYPOINT <command>
或ENTRYPOINT ["<executable>", "<param1>", "<param2>"...]
。 - 不可被 docker run 指定的参数覆盖,而且这些参数会被传递给 ENTRYPOINT 指定运行的程序。
--entrypoint
选项的参数可以覆盖 Dockerfile 的 ENTRYPOINT 指定的程序。
MAINTAINER "<your_name> <your@email.com>"
指明作者。
LABEL
命令生成键值对,也可以代替 MAINTAINER
命令,比如 LABEL maintainer="<your_name> <your@email.com>"
。
进入专用目录执行 docker build ./
开始构建镜像, docker build -t <RepoName>:<tagName> ./
可以指定镜像的所属仓库与标签。
5. docker 容器虚拟化网络
docker 被安装之后会自动创建一个叫 docker0 的 NAT 网桥。所有 docker 容器通过这个网桥与外界通信。
可以通过 sudo dnf install bridge-utils
或者 sudo apt install bridge-utils
获取工具来管理虚拟网桥。
每个使用 bridge 网络模式的容器被创建以后会生成 2 块虚拟网卡,一块连到 docker0 网桥上,一块连到容器里。
brctl show
( 命令来源于 bridge-utils 包 ) 可以看到 docker0 网桥上绑定的所有虚拟网卡。
执行 ip a
也能看见绑定在 docker0 上的虚拟网卡。
6. 常见问题
6.1. 设置容器内的时区
可以使用环境变量 TZ
设置时区。有些容器没做时区功能,这个环境变量对这些容器没有意义。
如果容器不支持用 TZ 来设置时区,那么把本地的时区文件挂载在容器里然后设置为只读 docker run ... -v /etc/localtime:/etc/localtime:ro ...
可能是一个解决方案。
6.2. 挂载 volume 时报错 failed to copy file info ...
有命令: docker run ... -v source_volume:/data/path ...
如果容器的 /data/path 目录下有文件或目录,那么 docker 会把 /data/path 中的数据拷贝到 source_volume 中,遇到同名文件时,保留 source_volume 中的数据。 以 root 的身份操作。
然而,有时,source_volume 是个 NFS volume,而 NFS 通常会用 root_squash 参数使得 NFS client 使用 root 身份读写数据时,把 NFS client 的身份变成 nobody。
这就意味着,在使用 NFS volume 的情况下 docker 会以 nobody 的身份来将 /data/path 中的数据拷贝到 source_volume 中,而通常 nobody 用户并没有这样的权限,docker 拷贝文件失败,这个报错就出现了。
要解决这个问题,需要使用: docker run ... -v source_volume:/data/path:nocopy ...
注意里面的 nocopy 参数。这个参数的含义是,在容器启动时,docker 不会将 /data/path 目录中的数据复制到 source_volume 中,而是直接进行覆盖式的挂载。这意味着,原本在 /data/path 目录下的内容不会出现在 source_volume 中。
这会产生一些影响: 如果容器内的程序需要原本存在于 /data/path 中的数据,那么这个参数不适合于这种情形。此时,需要将 source_volume 挂载到容器的其他位置,然后启动容器,最后在容器中手动拷贝数据。
由于是手动操作,所以用户可以自行在容器中选择一个有权限的身份。最后,再使用 nocopy 参数将 source_volume 覆盖式地挂载在 /data/path 上。
当然,还有一种情况: 容器中的程序检测到 /data/path 中的数据消失了,就会再生成一份数据放在 /data/path 上。此时,由于容器已经运行,重新生成的数据实际上被存储在 source_volume 中。
在这种情况下, nocopy 的覆盖式挂载不会有什么影响。
最后给一个例子: linuxserver/transmission 这个镜像的行为就是第二种情况所描述的。
用户不带 nocopy 参数地将 NFS volume 挂载到容器的 /config 目录,而容器的 /config 目录本身有数据,NFS 的配置参数里也有 root_squash。此时 docker 就会报挂载失败,因为 nobody 用户并没有拷贝数据到 NFS volume 的权限。
用户使用 nocopy 参数将 NFS volume 挂载到容器的 /config 目录,容器中的 transmission 检测到 /config 目录下没有配置文件就会重新生成一份配置文件放在 /config 目录下。transmission 重新生成配置文件并保存到 NFS volume 的流程中并不涉及 root 权限,所以配置文件可以成功被保存下来,容器也就可以正常启动。
当然,如果 NFS volume 中本来就有容器内程序需要的数据,那就不需要以上操作,直接挂载即可。
7. 卸载
首先卸载 Docker Engine
, CLI
以及 Containerd packages
: sudo apt-get purge docker-ce docker-ce-cli containerd.io
最后删除所有 images, containers 和 volumes: sudo rm -rf /var/lib/docker
apt-key list
找到 docker 的密钥手动删除。
sudo rm /etc/apt/sources.list.d/docker.list
删除 docker 的 apt 源。